package com.craigcmiller.cmtetris.core;

import java.util.Random;


/**
 * Game board handler
 * @author craig
 */
public class Board
{
	private int width, height;
	private Cell[][] cells;
	private Block block, nextBlock=null;
	private int updates=0;
	private static Random randomizer=new Random();
	private int level, rowsRemovedInLevel;
	private final int ROWS_PER_LEVEL=10;
	private boolean gameOver;
	private int score;
	
	
	/**
	 * Construct a new board with default number of cells (10x20)
	 * @return
	 */
	public static Board createDefaultBoard()
	{
		return new Board(10, 20);
	}
	
	
	/**
	 * Construct a board
	 * @param width Width in cells
	 * @param height Height in cells
	 */
	public Board(int width, int height)
	{
		this.width=width;
		this.height=height;
		
		this.gameOver=false;
		this.level=1;
		this.rowsRemovedInLevel=0;
		this.score=0;
		
		this.buildBoard();
		
		this.block=null;
	}
	
	
	/**
	 * Allocate and setup the board
	 */
	private void buildBoard()
	{
		cells=new Cell[getHeight()][getWidth()];
		
		// Create all the initial empty cells
		for (int i=0; i<getHeight(); i++) {
			for (int j=0; j<getWidth(); j++) {
				cells[i][j]=new Cell(Cell.CellState.empty, java.awt.Color.WHITE);
			}
		}
		
		// Prepopulate some cells
		/*for (int i=this.height-4; i<this.height; i++) {
			for (int j=0; j<this.width-1; j++) {
				cells[i][j].setCellColor(java.awt.Color.BLUE);
				cells[i][j].setCellState(Cell.CellState.set);
			}
		}*/
	}
	
	
	/**
	 * Perform a board update
	 */
	public void update()
	{
		// If the game is over do not continue with the update
		if (this.gameOver) return;
		
		// If there is no block yet create one
		if (this.block==null) this.addRandomBlock();
		
		this.block.moveDown();
		
		// Disable the current block so that updates do not effect it
		this.block.setCellStatesForBlock(Cell.CellState.empty);
		
		int rowsRemovedInUpdate=0;
		
		// Check for any lines to eliminate
		for (int i=cells.length-1; i>=0; i--) {
			boolean rowComplete=true;
			
			for (Cell c : cells[i])
				if (c.getCellState() !=Cell.CellState.set) {
					rowComplete=false;
					break; // There is an empty cell so this row is not yet full
				}
			
			if (!rowComplete) continue;
			
			// All cells are set so move all rows above down
			for (int j=i; j>0; j--) {
				for (int k=0; k<width; k++)
					if (cells[j][k].getCellState()==Cell.CellState.set) cells[j][k].copyPropertiesFromCell(cells[j-1][k]);
			}
			i++; // Increment i since all rows have moved down 1
			
			rowsRemovedInUpdate++;
			
			// Check if the level is completed
			if (++rowsRemovedInLevel==ROWS_PER_LEVEL) {
				// Up the level
				level++;
				rowsRemovedInLevel=0;
				
				System.out.println("Level upping to " + level);
			}
		}
		
		// Reenable the current block
		this.block.setCellStatesForBlock(Cell.CellState.set);
		
		updateNextBlock();
		
		// Handle increasing the score
		switch (rowsRemovedInUpdate) {
			case 0:
				break;
			case 1:
				score+=10;
				break;
			case 2:
				score+=25;
				break;
			case 3:
				score+=50;
				break;
			case 4:
				score+=100;
				break;
		}
		
		//System.out.println("Rows removed in update: " + rowsRemovedInUpdate);
		//System.out.println("Score: " + this.score);
		
		updates++;
	}
	
	
	/**
	 * Try and show the next block if the current one is away from it
	 */
	public void updateNextBlock()
	{
		// Show the next block if the current block has moved out its way
		if (this.nextBlock.isBlockAbove(this.block)) this.nextBlock.showAsNextBlock();
	}
	
	
	/**
	 * Set the game over
	 */
	public void setGameOver()
	{
		this.gameOver=true;
	}
	
	
	/**
	 * Returns true if the game is over
	 * @return
	 */
	public boolean getIsGameOver()
	{
		return this.gameOver;
	}
	
	
	/**
	 * Add a block to the board. There can only ever be one
	 * @param block
	 */
	public void setBlock(Block block)
	{
		this.block=block;
	}
	
	
	/**
	 * Get the active block. For testing only
	 * @return
	 */
	public Block getActiveBlock()
	{
		return this.block;
	}
	
	
	/**
	 * Add a random block to the board
	 */
	public void addRandomBlock()
	{
		if (nextBlock==null) nextBlock=this.createNextBlock();
		
		nextBlock.activateBlock();
		this.setBlock(nextBlock);
		
		nextBlock=this.createNextBlock();
	}
	
	
	/**
	 * Get the next block
	 * @return
	 */
	private Block createNextBlock()
	{
		Block newBlock;
		
		switch (Board.getRandomizer().nextInt(7)) {
			case 0:
				newBlock=new IBlock(this);
				break;
			case 1:
				newBlock=new OBlock(this);
				break;
			case 2:
				newBlock=new LBlock(this);
				break;
			case 3:
				newBlock=new ReverseLBlock(this);
				break;
			case 4:
				newBlock=new TBlock(this);
				break;
			case 5:
				newBlock=new ZBlock(this);
				break;
			case 6:
				newBlock=new ReverseZBlock(this);
				break;
			default:
				newBlock=null;
		}
		
		return newBlock;
	}
	
	
	/**
	 * Get a cell at a specific location
	 * @param x
	 * @param y
	 * @return A cell or null if the range is outwith the board
	 */
	public Cell getCellAt(int x, int y)
	{
		if (x<0 || x>=this.width || y<0 || y>=this.height) return null;
		
		return cells[y][x];
	}
	
	
	/**
	 * Get the game randomizer
	 * @return
	 */
	public static Random getRandomizer()
	{
		return randomizer;
	}
	
	
	/**
	 * Get the current level
	 * @return
	 */
	public int getLevel()
	{
		return level;
	}
	
	
	/**
	 * Get the current score
	 * @return
	 */
	public int getScore()
	{
		return score;
	}
	
	
	/**
	 * Get the time (in milliseconds) there should be between updates
	 * @return Value in milliseconds or -1 if the game is over
	 */
	public int getTimeBetweenUpdates()
	{
		if (this.gameOver) return -1;
		
		return (int)(1000/(level*0.8));
	}
	
	
	/**
	 * Get a string representation of the board
	 */
	@Override
	public String toString()
	{
		StringBuilder sb=new StringBuilder("---- Board (");
		sb.append(updates);
		sb.append(") ----\n");
		
		for (Cell[] ca : cells) {
			for (Cell c : ca) {
				sb.append(c.toString());
			}
			
			sb.append('\n');
		}
		
		return sb.toString();
	}
	
	
	/**
	 * Get the width (in cells) of the board
	 * @return
	 */
	public int getWidth()
	{
		return width;
	}
	
	
	/**
	 * Get the height (in cells) of the board
	 * @return
	 */
	public int getHeight()
	{
		return height;
	}
	
	
	/**
	 * Get the number of times the board has been updated
	 * @return
	 */
	public int getUpdates()
	{
		return updates;
	}
}
